﻿using System;
using System.Windows.Forms;
using System.Diagnostics;
using System.Linq;
using CppUT.Common;
using System.Collections.Generic;
using System.Messaging;
using System.ComponentModel;
using System.Drawing;

namespace TestRunnerGui
{
    public enum TestStatus
    {
        Fresh = 0,
        Pass,
        Fail
    }

    public partial class TestViewControl : UserControl
    {
        public TestViewControl()
        {
            InitializeComponent();

            //  Initialize the communication channels.
            _clientMessageCommunicator = new TestDataCommunicator(Constants.ClientMessagesQueue, CommunicationType.Transmit);
            _serverMessageCommunicator = new TestDataCommunicator(Constants.ServerMessagesQueue, CommunicationType.Receive);

            //  Add images
            var imageList = new ImageList();
            var fresh = Properties.Resources.Fresh;
            var pass = Properties.Resources.Pass;
            var fail = Properties.Resources.Fail;

            imageList.Images.Add(fresh);
            imageList.Images.Add(pass);
            imageList.Images.Add(fail);
            imageList.TransparentColor = Color.Magenta;

            treeView1.ImageList = imageList;
            treeView1.ImageIndex = 0;
            treeView1.SelectedImageIndex = 0;
            treeView1.Font = new Font("Consolas", 10.0f);

            _treeViewFiller = new BackgroundWorker();
            _treeViewFiller.DoWork += TreeViewFillerDoWork;
        }

        private void loadDLLToolStripMenuItem_Click(object sender, EventArgs e)
        {
            //  Check if message queue is available
            Log.MethodEntry();
            try
            {
                LoadAndInitialize();
            }
            catch (Exception ex)
            {
                ErrorManager.LogException(ex);
                throw;
            }

            Log.MethodExit();
        }

        private void reloadDLLToolStripMenuItem_Click(object sender, EventArgs e)
        {
            //  Close the already running server process.
            ProcessManager.KillProcess(Constants.CppUnitTestRunnerExe);

            //  Clear the existing nodes
            treeView1.Nodes.Clear();

            //  Reload the same DLL.
            StartNewServer(_currentLoadedDll);
        }

        private void LoadAndInitialize()
        {
            Log.MethodEntry();
            ProcessManager.KillProcess(Constants.CppUnitTestRunnerExe);

            var openFileDialog = new OpenFileDialog
            {
                Filter = "Test dll (*.dll)|*.dll",
                RestoreDirectory = true
            };

            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                //  Start the runner process which will load the DLL.
                _serverMessageCommunicator.MessageAvailable += ServerMessageCommunicatorMessageAvailable;
                _currentLoadedDll = openFileDialog.FileName;

                StartNewServer(_currentLoadedDll);
            }

            Log.MethodExit();
        }

        private void StartNewServer(string _currentLoadedDll)
        {
            var testRunnerProc = new Process
            {
                StartInfo =
                    new ProcessStartInfo(Constants.CppUnitTestRunnerExe, "\"" + _currentLoadedDll + "\"")
            };

            testRunnerProc.StartInfo.UseShellExecute = false;
            testRunnerProc.StartInfo.CreateNoWindow = true;

            //  Receive test metadata from the runner process
            _clientMessageCommunicator.SendMessage(TestMessageValues.GetTestMetaData, "");
            testRunnerProc.Start();
        }

        private void ServerMessageCommunicatorMessageAvailable(System.Messaging.Message message)
        {
            Log.MethodEntry();
            try
            {
                ProcessMessage(message);
            }
            catch (ArgumentException argEx)
            {
                ErrorManager.LogException(argEx);
                throw;
            }

            Log.MethodExit();
        }

        private void ProcessMessage(System.Messaging.Message aMessage)
        {
            Log.MethodEntry();
            try
            {
                switch (aMessage.Label)
                {
                    case TestMessageValues.TestMetaDataAvailable:
                        {
                            aMessage.Formatter = new XmlMessageFormatter(new[] {typeof (List<string>)});
                            RefreshTestData(aMessage.Body as List<string>);
                            break;
                        }
                    case TestMessageValues.TestResult:
                        {
                            aMessage.Formatter = new XmlMessageFormatter(new[] {typeof (TestResult)});
                            var testResult = aMessage.Body as TestResult;
                            SetStatusOfTreeNode(testResult);
                            break;
                        }
                    default:
                        {
                            throw new ArgumentException(ErrorManager.Messages.MessageLabelUnrecognized +
                                                        Environment.NewLine + "Input message: " + aMessage.Label);
                        }
                }
            }
            catch(Exception ex)
            {
                ErrorManager.LogException(ex);
                throw;
            }

            Log.MethodExit();
        }

        private void SetStatusOfTreeNode(TestResult testResult)
        {
            var testToUpdate = testResult.TestName;
            var rootNode = treeView1.Nodes[0];
            var rootTestSuite = (TestSuite)rootNode.Tag;
            for (int i = 0; i < rootTestSuite.Count; ++i)
            {
                if (testToUpdate.StartsWith(rootTestSuite.Children[i].Name + "::"))
                {
                    var treeNodeSuite = rootNode.Nodes[i];
                    var treeNodes = treeNodeSuite.Nodes;
                    for (int j = 0; j < treeNodes.Count; ++j)
                    {
                        if (treeNodes[j].Text.Equals(testToUpdate))
                        {
                            treeView1.Invoke(new SetTestStatusHandler(SetTestNodeStatus), treeNodes[j], testResult);
                            break;
                        }
                    }
                }
            }
        }

        private static void SetTestNodeStatus(TreeNode treeNode, TestResult testResult)
        {
            int statusImageIndex = testResult.TestResultStatus ? (int) TestStatus.Pass : (int) TestStatus.Fail;

            treeNode.ImageIndex = treeNode.SelectedImageIndex 
                = treeNode.Parent.ImageIndex = treeNode.Parent.SelectedImageIndex = statusImageIndex;
        }

        private void RefreshTestData(List<string> aMessageBody)
        {
            Log.MethodEntry();

            //  Build the test hierarchy
            _testHierarchyBuilder = new TestBuilder(aMessageBody);
            var testRoot = _testHierarchyBuilder.Build();

            //  Create/refresh the tree view control
            _treeViewFiller.RunWorkerCompleted += _treeViewFiller_RunWorkerCompleted;
            _treeViewFiller.RunWorkerAsync(testRoot);

            Log.MethodExit();
        }

        private void TreeViewFillerDoWork(object sender, DoWorkEventArgs e)
        {
            var testRoot = e.Argument as Test;
            Debug.Assert(null != testRoot);
            treeView1.Nodes.Clear();
            treeView1.Invoke(new AddTestsToTreeView(AddTests), testRoot);
        }

        private void AddTests(Test rootTest)
        {
            var testQueue = new Queue<Test>();
            var treeNodeQueue = new Queue<TreeNode>();

            testQueue.Enqueue(rootTest);

            var rootTreeNode = new TreeNode(rootTest.Name);
            rootTreeNode.Tag = rootTest;
            treeNodeQueue.Enqueue(rootTreeNode);
            treeView1.Nodes.Add(treeNodeQueue.Peek());

            while (testQueue.Count != 0)
            {
                var currentTest = testQueue.Dequeue();
                var currentTreeNode = treeNodeQueue.Dequeue();

                var testSuite = currentTest as TestSuite;
                if (null != testSuite)
                {
                    foreach (var test in testSuite.Children)
                    {
                        var treeNode = new TreeNode(test.Name);
                        if (test is TestSuite)
                        {
                            testQueue.Enqueue(test);
                            treeNodeQueue.Enqueue(treeNode);
                        }

                        treeNode.Tag = test;
                        currentTreeNode.Nodes.Add(treeNode);
                    }
                }
            }

            treeView1.Nodes[0].Expand();
        }

        private void _treeViewFiller_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            //  Tree view initialized
            toolStripStatusLabel1.Text = "Test DLL loaded.";
        }

        private void runTests_Click(object sender, EventArgs e)
        {
            //  Send the "Run tests" signal to the runner process
            var selectedTreeNode = treeView1.SelectedNode;
            var testToRun = selectedTreeNode.Tag as Test;
            Debug.Assert(null != testToRun);

            if (testToRun is TestSuite)
            {
                var testSuiteToRun = (TestSuite)testToRun;
                for (int i = 0; i < testSuiteToRun.Count; ++i)
                {
                    var test = testSuiteToRun.Children[i];
                    _clientMessageCommunicator.SendMessage(TestMessageValues.RunTest, test.Name);
                }
            }
            else
            {
                _clientMessageCommunicator.SendMessage(TestMessageValues.RunTest, testToRun.Name);
            }
        }

        public void Dispose()
        {
            _serverMessageCommunicator.Close();
            _clientMessageCommunicator.Close();
        }

        internal void TerminateTestRunnerApplication()
        {
            _clientMessageCommunicator.SendMessage(TestMessageValues.TerminateTestRunner, "");
        }

        private void toolStripFindTestTextBox_Enter(object sender, EventArgs e)
        {
            if (toolStripFindTestTextBox.Text.Equals("Find test.."))
            {
                toolStripFindTestTextBox.Text = string.Empty;
            }
        }

        private void toolStripFindTestTextBox_Leave(object sender, EventArgs e)
        {
            if (toolStripFindTestTextBox.Text.Equals(""))
            {
                toolStripFindTestTextBox.Text = "Find test..";
            }
        }

        private TestDataCommunicator _serverMessageCommunicator;
        private TestDataCommunicator _clientMessageCommunicator;
        private TestBuilder _testHierarchyBuilder;
        private BackgroundWorker _treeViewFiller;
        private string _currentLoadedDll;

        private delegate void AddTestsToTreeView(Test test);

        private delegate void SetTestStatusHandler(TreeNode treeNode, TestResult testResult);
    }
}
